package it.graph;

import java.awt.Color;
import java.awt.geom.Rectangle2D;
import java.util.Iterator;
import java.util.Set;

import javax.swing.BorderFactory;
import javax.swing.border.BevelBorder;
import javax.swing.border.Border;

import org.jgraph.graph.AttributeMap;
import org.jgraph.graph.DefaultEdge;
import org.jgraph.graph.DefaultGraphCell;
import org.jgraph.graph.GraphConstants;
import org.jgrapht.Graph;
import org.jgrapht.ext.JGraphModelAdapter;
import org.jgrapht.graph.DefaultWeightedEdge;
import org.jgrapht.graph.ListenableUndirectedWeightedGraph;
import org.jgrapht.traverse.DepthFirstIterator;


public class Grafo {
	
	//Ogni percorso ha bisogno di un contatore da inizializzare
	private static int NUM_NODI_PERCORSO = 0;
	//Usato per dare un identificatore ad ogni nodo
	private static int ID_NODO = 1;
	//Distanza massima entro cui un nodo non viene generato
	private static final Double MIN_DIST = 0.001;
	//Distanza massima consentita da ogni nodo
	private final Double maxDist;
	
	//Generazione delle caratteristiche grafiche dei nodi
	private static AttributeMap attrV;
	static {
		attrV = new AttributeMap();
		//Bordi Rialzati
        Border b = BorderFactory.createBevelBorder(BevelBorder.RAISED);
        GraphConstants.setBorder(attrV, b);
        //Disabilita spostamento e selezione
        GraphConstants.setMoveable(attrV, false);
        GraphConstants.setSelectable(attrV, false);
        //Colore del foreground
        GraphConstants.setForeground(attrV, Color.DARK_GRAY);
        //Colore del gradiente
        GraphConstants.setGradientColor(attrV, Color.ORANGE);
        //Colore del background
        GraphConstants.setBackground(attrV, Color.RED);
	}
	
	//Generazione delle caratteristiche grafiche dei lati
	private static AttributeMap attrE;
	static {
		attrE = new AttributeMap();
		//Disabilita spostamento e selezione
		GraphConstants.setMoveable(attrE, false);
		GraphConstants.setSelectable(attrE, false);
		//Etichetta disegnata lungo il lato
		GraphConstants.setLabelAlongEdge(attrE, true);
		//Larghezza della linea
		GraphConstants.setLineWidth(attrE, 2);
		//Colore linea
		GraphConstants.setLineColor(attrE, Color.BLUE);
	}

	//Ultimo nodo aggiunto
	private ConvPoint lastVertex = null;
	//Istanza del grafo
	private ListenableUndirectedWeightedGraph<ConvPoint,DefaultWeightedEdge> grafo;
	//Adapter grafico per JGraph
	private JGraphModelAdapter<ConvPoint,DefaultWeightedEdge> adapter;
	
	public Grafo() {
		
		grafo = new ListenableUndirectedWeightedGraph<ConvPoint,
					DefaultWeightedEdge>(DefaultWeightedEdge.class);
		adapter = new JGraphModelAdapter<ConvPoint,
					DefaultWeightedEdge>(grafo);
		
		//Inizializzazione distanza massima, raddoppia
		//per ogni livello di zoom
		maxDist = MIN_DIST * (2 << (21 - GUI.zoom));
		System.out.println("Distanza minima: " + maxDist);
		
	}
	
	public JGraphModelAdapter<ConvPoint,DefaultWeightedEdge> getGraphAdapter() {
		return adapter;
	}
	
	public Graph<ConvPoint,DefaultWeightedEdge> getGrafo() {
		return grafo;
	}
	
	public Set<ConvPoint> getVertex() {
		return grafo.vertexSet();
	}
	
	public Set<DefaultWeightedEdge> getEdge() {
		return grafo.edgeSet();
	}
	
	//Decide se aggiungere un nodo o integrare un lato nel grafo di
	//riferimento
	public void addClickedPoint(ConvPoint c){
	
		ConvPoint vicinoVertex = ceVicino(c);
				
		if(vicinoVertex != null){
			// se il punto indicato è vicino ad uno esistente controlliamo se il
			// punto attuale è il primo di un nuovo percorso non aggiungiamo lati
			if( lastVertex != vicinoVertex && NUM_NODI_PERCORSO > 0)
				//controlliamo se aggiungere un lato (se non esistente)
				if(!grafo.containsEdge(lastVertex, vicinoVertex))
					addEdge(vicinoVertex, lastVertex);
			
			lastVertex = vicinoVertex;
		} else {
			
			c.setId(ID_NODO++);
			grafo.addVertex(c);
			posizionamentoVertice(c);
			
			if(NUM_NODI_PERCORSO > 0 && grafo.vertexSet().size() != 1) {
				addEdge(c,lastVertex);
			}
			
			lastVertex = c;
		}
		NUM_NODI_PERCORSO++;
	}
	
	//controllo che non sia vicino ad un punto esistente, se esiste
	//restituisce il vicino, altrimenti null
	public ConvPoint ceVicino(ConvPoint c) {
		
		Double dis;
		int iter = 1;
		
		for(ConvPoint i: grafo.vertexSet()){
			dis = InfoMaps.calcolaDistanzaKM(i, c);
			System.err.println("Km da punto num. "+ (iter++) +" :" +
						InfoMaps.format3(dis) + " (" + dis + ")" );
			
			if(dis < maxDist)
				return i;
		}
		
		return null;
		
	}
	
	//Istanzia un nuovo lato calcolando il peso
	public void addEdge(ConvPoint a, ConvPoint b) {
		
		DefaultWeightedEdge e = new DefaultWeightedEdge();
		grafo.setEdgeWeight(e, InfoMaps.calcolaDistanzaKM(a, b));
		grafo.addEdge(a, b, e);
		etichettaLato(e);
		
	}
	
	//Posizionamento grafico del vertice sull'adapter
	public void posizionamentoVertice(ConvPoint v) {
		
		DefaultGraphCell cell = adapter.getVertexCell(v);

        Rectangle2D bounds =
            new Rectangle2D.Double(
                v.getXpx() - 15,
                v.getYpx() - 15,
                30,30);
        
        GraphConstants.setBounds(attrV, bounds);
        
        AttributeMap cellAttrV = new AttributeMap();
        cellAttrV.put(cell, attrV);
        adapter.edit(cellAttrV, null, null, null);

//      Stampa i dati della mappa degli attributi
//		for (Object i: cellAttrV.keySet())
//			System.out.println("Key: " + i);
//		for (Object i: cellAttrV.values())
//			System.out.println("Value: " + i);
		
	}
	
	//Imposta l'etichetta e le caratteristiche del lato
	public void etichettaLato(DefaultWeightedEdge e) {
		
		DefaultEdge cell = adapter.getEdgeCell(e);
		
		//Capisco perchè Molendini non ce l'ha fatta
		//è un macello, non è developer-friendly
		//la javadoc. Comunque è un coglione!
		cell.setUserObject(InfoMaps.format3(grafo.getEdgeWeight(e)) + " Km");

		AttributeMap cellAttrE = new AttributeMap();
		cellAttrE.put(cell, attrE);
		adapter.edit(cellAttrE, null, null, null);
		
	}
	
	//Ricerca il vertice con id passato come parametro
	public ConvPoint cercaVertice(int id) {
		for (ConvPoint i: getVertex())
			if (i.getId() == id)
				return i;
		return null;
	}
	
	//Colora i lati selezionati di nero e aumentando lo spessore
	public void coloraLato(DefaultWeightedEdge e, Color color) {
		
		DefaultEdge cell = adapter.getEdgeCell(e);
		AttributeMap c = cell.getAttributes();
		
		//Colora il lato di verde e aumenta lo spessore della linea
		GraphConstants.setLineColor(c, color);
		GraphConstants.setLineWidth(c, 8);
		
		AttributeMap cellAttrE = new AttributeMap();
		cellAttrE.put(cell, c);
		adapter.edit(cellAttrE, null, null, null);
		
	}
	
	//Reset degli attributi di ogni lato
	public void resetAttributiLati() {
		for (DefaultWeightedEdge i: getEdge())
			etichettaLato(i);
	}
	
	//Metodo che stampa sullo standard output il grafo generato
	public void stampaGrafo() {
		
		Iterator<ConvPoint> iter =
				new DepthFirstIterator<ConvPoint, DefaultWeightedEdge>(grafo);
		ConvPoint vertex;
		while (iter.hasNext()) {
			vertex = iter.next();
			System.out.println("Il vertice " + vertex.getId() + " è connesso a: ");
			for (DefaultWeightedEdge i: grafo.edgesOf(vertex))
				System.out.println("\t|" + grafo.getEdgeSource(i).getId() + ":" +
						grafo.getEdgeTarget(i).getId() + "| con peso " +
						InfoMaps.format3(grafo.getEdgeWeight(i)) + " Km.");
			System.out.println();
		}
		
	}
	
	public static int getNodiPercorso() {
		return NUM_NODI_PERCORSO;
	}
	
	//Reset del contatore del percorso
	public static void resetNodiPercorso() {
		NUM_NODI_PERCORSO = 0;		
	}
	
	//Reset del contatore degli id
	public static void resetIdNodo() {
		ID_NODO = 1;
	}

}
